package blackstar.plugin.ui;

import java.io.File;
import java.io.IOException;
import java.lang.management.CompilationMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.lang.management.MonitorInfo;
import java.lang.management.RuntimeMXBean;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.sql.Timestamp;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;

import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import blackstar.agent.AgentConfig;
import blackstar.agent.jvm.JMXProxy;
import blackstar.agent.jvm.JMXProxyManager;
import blackstar.agent.service.ServiceFactory;
import blackstar.plugin.ui.vo.DeadLockInfo;
import blackstar.plugin.ui.vo.JMXProxyInfo;
import blackstar.plugin.ui.vo.OverViewInfo;
import blackstar.plugin.ui.vo.PerformanceAgentInfo;
import blackstar.plugin.ui.vo.SystemInformation;
import blackstar.plugin.ui.vo.ThreadCPUInfo;
import blackstar.plugin.ui.vo.ThreadInformation;
import blackstar.plugin.ui.vo.ThreadTraceInformation;

import com.sun.management.GarbageCollectorMXBean;
import com.sun.management.OperatingSystemMXBean;

/**
 * @author RayWoo (ayufox@gmail.com)
 * 
 */
public class JMXProxyUtils
{
	private final static Log LOGGER = LogFactory.getLog(JMXProxyUtils.class);

	public static List<JMXProxyInfo> listProxys()
	{
		JMXProxyManager proxyManager = ServiceFactory.getJMXProxyManager();
		Collection<JMXProxy> list = proxyManager.listProxys();
		List<JMXProxyInfo> result = new ArrayList<JMXProxyInfo>();
		for (JMXProxy proxy : list)
		{
			result.add(getProxyInfo(proxy.getPid()));
		}
		return result;
	}

	public static JMXProxyInfo getProxyInfo(String jvmid)
	{
		JMXProxy proxy = getJMXProxy(jvmid);
		if (proxy == null)
		{
			return null;
		}

		JMXProxyInfo jvmInfo = new JMXProxyInfo();
		jvmInfo.setId(proxy.getId());
		jvmInfo.setPid(proxy.getPid());
		Object serviceURL = proxy.getProperties().get("JMXServiceURL");
		if (serviceURL != null)
		{
			jvmInfo.setServiceURL(serviceURL.toString());
		}
		File performanceDir = new File(AgentConfig.getPerformanceDataPath(proxy
				.getId()));
		if (performanceDir.exists() && performanceDir.isDirectory()
				&& performanceDir.list().length > 0)
		{
			jvmInfo.setPerformanceEnable(true);
		} else
		{
			jvmInfo.setPerformanceEnable(false);
		}

		return jvmInfo;
	}

	public static String getJMXProxyId(String jvmid)
	{
		JMXProxy proxy = getJMXProxy(jvmid);
		if (proxy == null)
		{
			return null;
		} else
		{
			return proxy.getId();
		}
	}

	private static JMXProxy getJMXProxy(String jvmid)
	{
		JMXProxyManager proxyManager = ServiceFactory.getJMXProxyManager();

		return proxyManager.getProxy(jvmid);
	}

	public static List<ThreadInformation> listThreadInofs(String jvmid)
			throws IOException
	{
		JMXProxy proxy = getJMXProxy(jvmid);
		if (proxy == null)
		{
			return null;
		}

		ThreadMXBean threadMXBean = ManagementFactory.newPlatformMXBeanProxy(
				proxy.getLocalMBeanServerConnection(),
				ManagementFactory.THREAD_MXBEAN_NAME, ThreadMXBean.class);
		OperatingSystemMXBean operatingSystemMXBean = ManagementFactory
				.newPlatformMXBeanProxy(proxy.getLocalMBeanServerConnection(),
						ManagementFactory.OPERATING_SYSTEM_MXBEAN_NAME,
						OperatingSystemMXBean.class);
		long[] threadids = threadMXBean.getAllThreadIds();
		ThreadInfo[] infos = threadMXBean.getThreadInfo(threadids);
		List<ThreadInformation> result = new ArrayList<ThreadInformation>();

		long uptime = operatingSystemMXBean.getProcessCpuTime();
		NumberFormat format = NumberFormat.getInstance();
		format.setMinimumFractionDigits(3);
		format.setMaximumFractionDigits(3);
		for (ThreadInfo info : infos)
		{
			ThreadInformation information = new ThreadInformation();
			information.setBlockedCount(info.getBlockedCount());
			information.setBlockedTime(info.getBlockedTime());
			information.setThreadCpuTime(threadMXBean.getThreadCpuTime(info
					.getThreadId()));
			information.setThreadId(info.getThreadId());
			information.setThreadName(info.getThreadName());
			information.setWaitedCount(info.getWaitedCount());
			information.setWaitedTime(info.getWaitedTime());
			String state = getState(info.getThreadState());
			information.setState(state);
			information.setCpuTimePercent(format.format((double) information
					.getThreadCpuTime()
					* 100 / uptime));
			result.add(information);
			information = null;
		}
		Collections.sort(result, new Comparator<ThreadInformation>()
		{
			public int compare(ThreadInformation o1, ThreadInformation o2)
			{
				return new Long(o1.getThreadId()).compareTo(o2.getThreadId());
			}
		});
		return result;
	}

	private static String getState(Thread.State threadState)
	{
		String state = "";
		switch (threadState)
		{
		case NEW:
			state = "New";
			break;
		case RUNNABLE:
			state = "Runnable";
			break;
		case BLOCKED:
			state = "Blocked";
			break;
		case WAITING:
			state = "Waiting";
			break;
		case TIMED_WAITING:
			state = "Timed_Waiting";
			break;
		case TERMINATED:
			state = "Terminated";
			break;
		}
		return state;
	}

	public static ThreadCPUInfo getThreadCPUInfo(String jvmid, long threadId)
			throws IOException
	{
		if (threadId <= 0)
		{
			return null;
		}

		JMXProxy proxy = getJMXProxy(jvmid);
		if (proxy == null)
		{
			return null;
		}

		ThreadMXBean threadMXBean = ManagementFactory.newPlatformMXBeanProxy(
				proxy.getLocalMBeanServerConnection(),
				ManagementFactory.THREAD_MXBEAN_NAME, ThreadMXBean.class);

		long threadCPUTime = threadMXBean.getThreadCpuTime(threadId);
		if (threadCPUTime < 0)
		{
			return null;
		}

		RuntimeMXBean runtimeMXBean = ManagementFactory.newPlatformMXBeanProxy(
				proxy.getLocalMBeanServerConnection(),
				ManagementFactory.RUNTIME_MXBEAN_NAME, RuntimeMXBean.class);

		OperatingSystemMXBean operatingSystemMXBean = ManagementFactory
				.newPlatformMXBeanProxy(proxy.getLocalMBeanServerConnection(),
						ManagementFactory.OPERATING_SYSTEM_MXBEAN_NAME,
						OperatingSystemMXBean.class);

		ThreadCPUInfo result = new ThreadCPUInfo();
		result.setSystemCPU(runtimeMXBean.getUptime() * 1000 * 1000);
		result.setProcessCPU(operatingSystemMXBean.getProcessCpuTime());
		result.setThreadCPU(threadCPUTime);
		return result;
	}

	public static String getThreadName(String jvmid, long threadId)
			throws IOException
	{
		if (threadId <= 0)
		{
			return null;
		}

		JMXProxy proxy = getJMXProxy(jvmid);
		if (proxy == null)
		{
			return null;
		}

		ThreadMXBean threadMXBean = ManagementFactory.newPlatformMXBeanProxy(
				proxy.getLocalMBeanServerConnection(),
				ManagementFactory.THREAD_MXBEAN_NAME, ThreadMXBean.class);

		ThreadInfo threadInfo = threadMXBean.getThreadInfo(threadId);

		return ((threadInfo != null) ? threadInfo.getThreadName() : null);
	}

	public static ThreadTraceInformation getThreadTrace(String jvmid,
			long threadId) throws IOException
	{
		if (threadId <= 0)
		{
			return null;
		}
		JMXProxy proxy = getJMXProxy(jvmid);
		if (proxy == null)
		{
			return null;
		}

		ThreadMXBean threadMXBean = ManagementFactory.newPlatformMXBeanProxy(
				proxy.getLocalMBeanServerConnection(),
				ManagementFactory.THREAD_MXBEAN_NAME, ThreadMXBean.class);
		ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(new long[]
		{ threadId }, true, false);
		if (threadInfos == null || threadInfos.length == 0)
		{
			return null;
		}
		ThreadInfo threadInfo = threadInfos[0];

		ThreadTraceInformation result = new ThreadTraceInformation();
		result.setThreadId(threadInfo.getThreadId());
		result.setThreadName(threadInfo.getThreadName());
		result.setBlockedCount(threadInfo.getBlockedCount());
		result.setWaitedCount(threadInfo.getWaitedCount());
		result.setState(getState(threadInfo.getThreadState()));
		result.setLockName(threadInfo.getLockName());
		result.setLockOwnerName(threadInfo.getLockOwnerName());

		StackTraceElement[] traces = threadInfo.getStackTrace();
		MonitorInfo[] monitorLocks = threadInfo.getLockedMonitors();
		for (int i = 0; i < traces.length; i++)
		{
			StackTraceElement trace = traces[i];
			StringBuilder message = new StringBuilder();
			message.append(trace);

			for (int j = 0; j < monitorLocks.length; j++)
			{
				MonitorInfo monitor = monitorLocks[j];
				if (monitor.getLockedStackDepth() == i)
				{
					message.append("    Monitor locked(" + monitor + ")");
				}
				monitor = null;
			}
			result.addTrace(message.toString());
		}
		return result;
	}

	public static List<DeadLockInfo> listDeadLocks(String jvmid)
			throws IOException
	{
		JMXProxy proxy = getJMXProxy(jvmid);
		if (proxy == null)
		{
			return null;
		}

		ThreadMXBean threadMXBean = ManagementFactory.newPlatformMXBeanProxy(
				proxy.getLocalMBeanServerConnection(),
				ManagementFactory.THREAD_MXBEAN_NAME, ThreadMXBean.class);

		long[] deadLockIds = threadMXBean.findDeadlockedThreads();
		if (deadLockIds == null || deadLockIds.length == 0)
		{
			return Collections.EMPTY_LIST;
		}

		ThreadInfo[] deadLockThreads = threadMXBean.getThreadInfo(deadLockIds);

		List<DeadLockInfo> result = new ArrayList<DeadLockInfo>();
		for (ThreadInfo threadInfo : deadLockThreads)
		{
			DeadLockInfo lockInfo = new DeadLockInfo();
			lockInfo.setThreadId(threadInfo.getThreadId());
			lockInfo.setThreadName(threadInfo.getThreadName());
			lockInfo.setLockOwnerId(threadInfo.getLockOwnerId());
			lockInfo.setState(getState(threadInfo.getThreadState()));
			result.add(lockInfo);
			lockInfo = null;
		}
		return result;
	}

	public static SystemInformation getSystemInformation() throws IOException
	{
		MBeanServer server = ManagementFactory.getPlatformMBeanServer();
		OperatingSystemMXBean operatingSystemMXBean = ManagementFactory
				.newPlatformMXBeanProxy(server,
						ManagementFactory.OPERATING_SYSTEM_MXBEAN_NAME,
						OperatingSystemMXBean.class);
		;

		SystemInformation systemInfomration = new SystemInformation();
		systemInfomration.setOsName(operatingSystemMXBean.getName());
		systemInfomration.setOsArch(operatingSystemMXBean.getArch());
		systemInfomration.setOsVersion(operatingSystemMXBean.getVersion());
		systemInfomration.setOsProcessors(Integer
				.toString(operatingSystemMXBean.getAvailableProcessors()));
		double loadAverage = operatingSystemMXBean.getSystemLoadAverage();
		if (loadAverage < 0)
		{
			systemInfomration.setOsLoadAvarage("Unavailable");
		} else
		{
			systemInfomration.setOsLoadAvarage(Double.toHexString(loadAverage));
		}
		systemInfomration.setOsTotalMemorySize(operatingSystemMXBean
				.getTotalPhysicalMemorySize()
				/ (1024 * 1024) + "M");
		systemInfomration.setOsFreeMemorySize(operatingSystemMXBean
				.getFreePhysicalMemorySize()
				/ (1024 * 1024) + "M");
		systemInfomration.setOsVirtualMemorySize(operatingSystemMXBean
				.getCommittedVirtualMemorySize()
				/ (1024 * 1024) + "M");
		systemInfomration.setOsTotalSwapMemorySize(operatingSystemMXBean
				.getTotalSwapSpaceSize()
				/ (1024 * 1024) + "M");
		systemInfomration.setOsFreeSwapMemorySize(operatingSystemMXBean
				.getFreeSwapSpaceSize()
				/ (1024 * 1024) + "M");

		return systemInfomration;
	}

	public static OverViewInfo getJVMOverViewInfo(String jvmid)
			throws IOException
	{
		JMXProxy proxy = getJMXProxy(jvmid);
		if (proxy == null)
		{
			return null;
		}

		OverViewInfo overview = new OverViewInfo();

		Object serviceURL = proxy.getProperties().get("JMXServiceURL");
		if (serviceURL != null)
		{
			overview.setJmxServiceURL(serviceURL.toString());
		}

		RuntimeMXBean runtimeMXBean = ManagementFactory.newPlatformMXBeanProxy(
				proxy.getLocalMBeanServerConnection(),
				ManagementFactory.RUNTIME_MXBEAN_NAME, RuntimeMXBean.class);

		CompilationMXBean compilationMXBean = ManagementFactory
				.newPlatformMXBeanProxy(proxy.getLocalMBeanServerConnection(),
						ManagementFactory.COMPILATION_MXBEAN_NAME,
						CompilationMXBean.class);

		MemoryMXBean memoryMXBean = ManagementFactory.newPlatformMXBeanProxy(
				proxy.getLocalMBeanServerConnection(),
				ManagementFactory.MEMORY_MXBEAN_NAME, MemoryMXBean.class);

		SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
		overview.setRuntimeStartTime(format.format(new Timestamp(runtimeMXBean
				.getStartTime())));
		long upTime = runtimeMXBean.getUptime();
		long hour = upTime / (60 * 60 * 1000);
		long minute = upTime % (60 * 60 * 1000) / (60 * 1000);
		long second = upTime % (60 * 1000) / 1000;
		StringBuilder runtimeUpTime = new StringBuilder();
		if (hour > 0)
		{
			runtimeUpTime.append(hour + "hours ");
		}
		if (minute > 0)
		{
			runtimeUpTime.append(minute + "minutes ");
		}
		runtimeUpTime.append(second + "seconds");
		overview.setRuntimeUpTime(runtimeUpTime.toString());
		overview.setRuntimeName(runtimeMXBean.getName());
		overview.setRuntimeVmvendor(runtimeMXBean.getVmVendor());
		overview.setRuntimeVmversion(runtimeMXBean.getVmVersion());
		overview.setRuntimeVmname(runtimeMXBean.getVmName());
		List<String> jvmArgList = runtimeMXBean.getInputArguments();
		StringBuilder runtimeVmArgs = new StringBuilder();
		for (String key : jvmArgList)
		{
			runtimeVmArgs.append(key + " ");
		}
		overview.setRuntimeVmArgs(runtimeVmArgs.toString());
		overview.setRuntimeLibraryPath(runtimeMXBean.getLibraryPath());
		overview.setRuntimeClassPath(runtimeMXBean.getClassPath());
		overview.setRuntimeBootClassPath(runtimeMXBean.getBootClassPath());

		overview.setCompilationName(compilationMXBean.getName());
		long totalCompilationTime = compilationMXBean.getTotalCompilationTime();
		long compilationMinute = totalCompilationTime / (60 * 1000);
		long compilationSecond = totalCompilationTime % (60 * 1000) / 1000;
		StringBuilder compilationTime = new StringBuilder();
		if (compilationMinute > 0)
		{
			compilationTime.append(compilationMinute + "minutes ");
		}
		compilationTime.append(compilationSecond + "seconds");
		overview.setCompilationTime(compilationTime.toString());

		MemoryUsage heapMemoryUsage = memoryMXBean.getHeapMemoryUsage();
		String memoryHeap = "初始化:" + heapMemoryUsage.getInit() / (1024 * 1048)
				+ "M 最大:" + heapMemoryUsage.getMax() / (1024 * 1024) + "M 已分配:"
				+ heapMemoryUsage.getCommitted() / (1024 * 1024) + "M 已使用:"
				+ heapMemoryUsage.getUsed() / (1024 * 1024) + "M";
		MemoryUsage nonHeapMemoryUsage = memoryMXBean.getNonHeapMemoryUsage();
		String nonHeapMemoryHeap = "初始化:" + nonHeapMemoryUsage.getInit()
				/ (1024 * 1048) + "M 最大:" + nonHeapMemoryUsage.getMax()
				/ (1024 * 1024) + "M 已分配:" + nonHeapMemoryUsage.getCommitted()
				/ (1024 * 1024) + "M 已使用:" + nonHeapMemoryUsage.getUsed()
				/ (1024 * 1024) + "M";
		overview.setMemoryHeap(memoryHeap);
		overview.setMemoryNonheap(nonHeapMemoryHeap);

		try
		{
			ObjectName objectName = new ObjectName(
					"java.lang:type=GarbageCollector,*");
			Set<ObjectName> set = proxy.getLocalMBeanServerConnection()
					.queryNames(objectName, null);
			int index = 0;
			String[] memoryGcArray = new String[set.size()];
			for (ObjectName on : set)
			{
				StringBuilder memoryGcs = new StringBuilder();
				GarbageCollectorMXBean garbageCollectorMXBean = ManagementFactory
						.newPlatformMXBeanProxy(proxy
								.getLocalMBeanServerConnection(),
								on.toString(), GarbageCollectorMXBean.class);
				memoryGcs.append("名称:" + garbageCollectorMXBean.getName());
				memoryGcs.append(" 收集次数:"
						+ garbageCollectorMXBean.getCollectionCount());
				long collectionTime = garbageCollectorMXBean
						.getCollectionTime();
				long collectionMinute = collectionTime / (60 * 1000);
				long collectionSecond = collectionTime % (60 * 1000) / (1000);
				StringBuilder memoryGcTime = new StringBuilder();
				if (collectionMinute > 0)
				{
					memoryGcTime.append(collectionMinute + "minutes ");
				}
				memoryGcTime.append(collectionSecond + "seconds");
				;
				memoryGcs.append(" 总时间:" + memoryGcTime.toString());
				memoryGcs.append(" 内存块:[");
				int i = 0;
				for (String memory : garbageCollectorMXBean
						.getMemoryPoolNames())
				{
					if (i > 0)
					{
						memoryGcs.append(",");
					}
					memoryGcs.append(memory);
					i++;
				}
				memoryGcs.append("]");

				garbageCollectorMXBean = null;
				memoryGcArray[index] = memoryGcs.toString();
				index++;
			}

			overview.setMemoryGcs(memoryGcArray);

		} catch (MalformedObjectNameException e)
		{
			overview.setMemoryGcs(new String[0]);
		}
		return overview;
	}

	public static PerformanceAgentInfo getPerformanceAgentInfo(String jvmid)
			throws IOException
	{
		JMXProxy proxy = getJMXProxy(jvmid);
		if (proxy == null)
		{
			return null;
		}

		try
		{
			ObjectName objectName = new ObjectName(
					"BlackStar:type=InvokePerformance,*");
			Set<ObjectName> set = proxy.getLocalMBeanServerConnection()
					.queryNames(objectName, null);
			for (ObjectName on : set)
			{
				PerformanceAgentInfo info = new PerformanceAgentInfo();
				info.setType(on.getKeyProperty("name"));
				Long logTimes = (Long) proxy.getLocalMBeanServerConnection()
						.getAttribute(on, "LogTimes");
				info.setLogTimes(logTimes.toString());
				Long logTimeNano = (Long) proxy.getLocalMBeanServerConnection()
						.getAttribute(on, "LogTimeNano");
				if (logTimes == 0)
				{
					info.setLogAvarageTime("0.00ms/t");
				} else
				{
					NumberFormat format = NumberFormat.getInstance();
					format.setGroupingUsed(false);
					format.setMaximumFractionDigits(3);
					info.setLogAvarageTime(format.format((double) logTimeNano
							/ logTimes / 1000000)
							+ "ms/t");
				}
				Long logBytes = (Long) proxy.getLocalMBeanServerConnection()
						.getAttribute(on, "LogBytes");
				StringBuilder logBytesBuffer = new StringBuilder();
				long mLogBytes = logBytes / (1024 * 1024);
				long kLogBytes = logBytes % (1024 * 1024) / 1024;
				long bLogBytes = logBytes % 1024;
				if (mLogBytes > 0)
				{
					logBytesBuffer.append(mLogBytes + "M");
				}
				if (kLogBytes > 0)
				{
					logBytesBuffer.append(kLogBytes + "K");
				}
				if (bLogBytes > 0)
				{
					logBytesBuffer.append(bLogBytes + "B");
				}
				info.setLogBytes(logBytesBuffer.toString());

				Long bufferSize = (Long) proxy.getLocalMBeanServerConnection()
						.getAttribute(on, "BufferSize");
				if (bufferSize > 1024 * 1024)
				{
					info.setBufferSize((double) bufferSize / (1024 * 1024)
							+ "M");
				} else
				{
					info.setBufferSize((double) bufferSize / 1024 + "K");
				}

				String invokeMessage = (String) proxy
						.getLocalMBeanServerConnection().getAttribute(on,
								"InvokeMessage");
				info.setInvokeMessage(invokeMessage);

				return info;
			}
		} catch (Exception e)
		{
			LOGGER.error("error query performance info", e);
		}

		return null;
	}

	public static String reloadConfig(String jvmid)
	{
		JMXProxy proxy = getJMXProxy(jvmid);
		if (proxy == null)
		{
			return null;
		}
		return proxy.loadAgent("performance", "");
	}

	public static String statDetal(String jvmid, String date)
	{
		JMXProxy proxy = getJMXProxy(jvmid);
		if (proxy == null)
		{
			return null;
		}

		try
		{
			ObjectName objectName = new ObjectName(
					"BlackStar:type=Performance,name=DetailStat");

			MBeanServer server = ManagementFactory.getPlatformMBeanServer();

			if ("CurrentHour".equals(date))
			{
				return (String) server.invoke(objectName, "statCurrentHour",
						new Object[]
						{ proxy.getId() }, new String[]
						{ String.class.getName() });
			} else
			{
				return (String) server.invoke(objectName, "stat", new Object[]
				{ proxy.getId(), date }, new String[]
				{ String.class.getName(), String.class.getName() });
			}
		} catch (Exception e)
		{
			LOGGER.error("error query performance stat bean", e);

			return "PerformanceStat Bean Error";
		}
	}

	public static String statSimple(String jvmid, String date)
	{
		JMXProxy proxy = getJMXProxy(jvmid);
		if (proxy == null)
		{
			return null;
		}

		try
		{
			ObjectName objectName = new ObjectName(
					"BlackStar:type=Performance,name=SimpleStat");

			MBeanServer server = ManagementFactory.getPlatformMBeanServer();

			if ("CurrentHour".equals(date))
			{
				return (String) server.invoke(objectName, "statCurrentHour",
						new Object[]
						{ proxy.getId() }, new String[]
						{ String.class.getName() });
			} else
			{
				return (String) server.invoke(objectName, "stat", new Object[]
				{ proxy.getId(), date }, new String[]
				{ String.class.getName(), String.class.getName() });
			}
		} catch (Exception e)
		{
			LOGGER.error("error query performance stat bean", e);

			return "PerformanceStat Bean Error";
		}

	}
}
